#include "Camera.h"
#include "Cannon.h"
#include "Player.h"
#include <mmsystem.h>
#include <GL/glut.h>
#include "SDL/SDL.h"
#include "SDL/SDL_image.h"

//===============================================================================================================================
// Variables/Objects
//===============================================================================================================================

Camera cam; //Camera object
Player player; //Player object

int w,h; //width and height
float halfBox = 50.0f;

short int direction_speed = 1;

GLuint g_texture_obj[2];

float tileSize = 1.0f;
float bodysize = 0.75f;
float cannonSize = 0.5f;
short int numTiles = 30;

Cannon a1 (90, numTiles/2, 0); //left cannon
Cannon a2 (-90, numTiles/2, numTiles); //right cannon
Cannon a3 (0, 0, numTiles/2); //bottom cannon
Cannon a4 (180, numTiles, numTiles/2); //top cannon

short int points = 0;
short int life = 5;
float scale_var = 1;

bool gameOver = false;

char buffer[10];
//===============================================================================================================================
// FUNCTIONS (PROTOTYPES)
//===============================================================================================================================

void InitScene();

void Mouse(int button, int state, int x, int y);
void MouseMotion(int x, int y);

void NormalKeys(unsigned char key, int x, int y);
void DirectionKeys(int key, int x, int y);

void Update(int value);

void RenderScene(void);
void DrawKirby();
void DrawSkyBox();
void DrawGround();
void Shoot(Cannon &c);
bool PlayerProjCollision(Cannon &c);
bool BoundaryProjCollision(Cannon &c);

void ResetGame();

void ChangeWindowSize(int w1, int h1);

void WriteMenu();
void SetOrthographicProjection();
void RenderBitmapString(float x, float y, void *font, char *string);
void ResetPerspectiveProjection();

void loadTexture(GLuint texture_obj, const char *fileName);

void timer(int id);

//===============================================================================================================================
// MAIN
//===============================================================================================================================

int main(int argc, char **argv)
{
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);
	glutInitWindowPosition(100,100);
	glutInitWindowSize(1280,720);

	glutCreateWindow("Richard Wou & Cassie Wong - COMP 371 Final Project");

	InitScene();

	PlaySound(TEXT("FantasyMeadows.wav"), NULL, SND_FILENAME|SND_LOOP|SND_ASYNC);

	glutMouseFunc(Mouse);
    glutPassiveMotionFunc(MouseMotion);

	glutKeyboardFunc(NormalKeys);

	glutDisplayFunc(RenderScene);
	//glutIdleFunc(RenderScene);

	glutReshapeFunc(ChangeWindowSize);

	glutTimerFunc(40, Update, 0);

	glutMainLoop();

	return(0);
}

//===============================================================================================================================
// FUNCTIONS
//===============================================================================================================================

//----------------------------------------------------------------------
// INITIALIZATION
//----------------------------------------------------------------------
void InitScene() {
	glEnable(GL_DEPTH_TEST);
	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);
	glEnable(GL_LIGHT1);
	glEnable(GL_LIGHT_MODEL_AMBIENT);
	glEnable(GL_COLOR_MATERIAL);

	glEnable(GL_FOG);
	glFogi(GL_FOG_MODE, GL_LINEAR);

	glGenTextures(1, &g_texture_obj[0]);
    loadTexture(g_texture_obj[0], "brk.bmp");
	glGenTextures(1, &g_texture_obj[1]);
	loadTexture(g_texture_obj[1], "camo.bmp");

	player.Init(numTiles/2, -2*bodysize, numTiles/2, numTiles, bodysize);
	//glutFullScreen();
}

//----------------------------------------------------------------------
// MOUSE
//----------------------------------------------------------------------
void Mouse(int button, int state, int x, int y)
{
    if(state == GLUT_DOWN) {
        if(button == GLUT_LEFT_BUTTON) {
            cam.Reset();
        }
    }
}

void MouseMotion(int x, int y)
{
    static bool justWarped = false; //bool to prevent event from going into an infinite loop.

    if(justWarped) {
        justWarped = false;
        return;
    }

    int dx = x - w/2; //delta x
    int dy = y - h/2; //delta y

    if(dx) {
        cam.RotateYaw(dx);
    }

    if(dy) {
        cam.RotatePitch(dy);
    }

    glutWarpPointer(w/2, h/2);

    justWarped = true;
}

//----------------------------------------------------------------------
// KEYBOARD
//----------------------------------------------------------------------
void NormalKeys(unsigned char key, int x, int y) {
	if (key == 27 ) exit(0);

	if(key == 'w' || key == 'W')
	{
		player.RotateFace(0);
		player.MoveFrontBack(direction_speed);
	}

	if (key == 'a' || key =='A')
	{
		player.RotateFace(1);
		player.MoveLeftRight(-direction_speed);
	}

	if(key == 's' || key == 'S')
	{
		player.RotateFace(2);
		player.MoveFrontBack(-direction_speed); 
	}

	if(key == 'd' || key == 'D')
	{
		player.RotateFace(3);
		player.MoveLeftRight(direction_speed);
	}

	if(key == 'r' && gameOver == true)
		ResetGame();

}

void Update(int value)
{
	if(life <= 0)
		gameOver = true;

	else
	{
		points += 5;

		if(points%2000 == 0)
		{
			scale_var += 0.5f;
			player.UpdatePlayerSize(bodysize*scale_var);
		}
	}


	glutPostRedisplay();
	glutTimerFunc(40, Update, 0);
}

//----------------------------------------------------------------------
// RENDER SCENE
//----------------------------------------------------------------------

void RenderScene(void) {
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	
	//Add ambient light
	GLfloat ambientIntensity[] = {0.2f, 0.2f, 0.2f, 1.0f};//Color/Intensity (0.2,0.2,0.2)
	glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambientIntensity);
	
	//Add positioned light
	GLfloat light_Pos0[] = {30.0f, 20.0f,30.0f, 1.0f}; //Positioned at (30,20,38)
	GLfloat light_Ka0[4] = {0.1, 0.1, 0.1, 1.0}; //ambient light parameters
	GLfloat light_Kd0[4] = {0.8, 0.8, 0.8, 1.0}; //diffuse light parameters
	GLfloat light_Ks0[4] = {0.9, 0.9, 0.9, 1.0}; //specular light parameters
	glLightfv(GL_LIGHT0, GL_POSITION, light_Pos0);
    glLightfv(GL_LIGHT0, GL_AMBIENT, light_Ka0);
	glLightfv(GL_LIGHT0, GL_DIFFUSE, light_Kd0);
	glLightfv(GL_LIGHT0, GL_SPECULAR, light_Ks0);
	
	//Add directed light
	GLfloat light_Pos1[] = {-15.0f, 5.0f, 15.0f, 0.0f};//Coming from direction (-15,5,15)
	GLfloat light_Ka1[4] = {0.1, 0.1, 0.1, 1.0};  //ambient light parameters
	GLfloat light_Kd1[4] = {0.8, 0.8, 0.8, 1.0}; //diffuse light parameters
	GLfloat light_Ks1[4] = {0.5, 0.5, 0.5, 1.0}; //specular light parameters
	glLightfv(GL_LIGHT1, GL_POSITION, light_Pos1);
    glLightfv(GL_LIGHT1, GL_AMBIENT, light_Ka1);
	glLightfv(GL_LIGHT1, GL_DIFFUSE, light_Kd1);
	glLightfv(GL_LIGHT1, GL_SPECULAR, light_Ks1);
	
	GLfloat fogColor[4] = {0.85, 0.85, 0.85, 1.0};
    //glFogi(GL_FOG_MODE, GL_LINEAR );
    glFogfv(GL_FOG_COLOR, fogColor);
    glFogf(GL_FOG_START, 30.0 );
    glFogf(GL_FOG_END, 75.0 );
	glFogf(GL_FOG_DENSITY, 0.008f );


	glColor3f(0.1960785,0.6,0.8); //SkyBlue = color red 0.196078 green 0.6 blue 0.8

	DrawSkyBox();
	DrawGround();
	
	a1.DrawCannon(g_texture_obj[1]);
	a2.DrawCannon(g_texture_obj[1]);
	a3.DrawCannon(g_texture_obj[1]);
	a4.DrawCannon(g_texture_obj[1]);

	Shoot(a1);
	Shoot(a2);
	Shoot(a3);
	Shoot(a4);

	glColor3f(0.0f, 1.0f, 0.0f);

	glPushMatrix();
		//glTranslatef(cam.GetX()+5, 0, cam.GetZ()+5);
		glTranslatef(player.getX(), player.getY(), player.getZ());
		glRotatef(player.getFace(), 0, 1.0f, 0);
		glScalef(scale_var, scale_var, scale_var);
		DrawKirby();
	glPopMatrix();

	WriteMenu();

	glutSwapBuffers();
}

void DrawKirby() {

	/*glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D,g_texture_obj[0]);

	glColor3f(0.8f, 0.5f, 0.5f);
	glBegin(GL_QUADS);
	
	//Front
	glNormal3f(0.0f, 0.0f, 1.0f);
	glTexCoord2f (0,1); glVertex3f(-bodysize, -bodysize, bodysize);
	glTexCoord2f (1,1); glVertex3f(bodysize, -bodysize, bodysize);
	glTexCoord2f (1,0); glVertex3f(bodysize, bodysize, bodysize);
	glTexCoord2f (0,0); glVertex3f(-bodysize, bodysize, bodysize);

	//Right
	glNormal3f(1.0f, 0.0f, 0.0f);
	glTexCoord2f (0,1); glVertex3f(bodysize, -bodysize, -bodysize);
	glTexCoord2f (0,0); glVertex3f(bodysize, bodysize, -bodysize);
	glTexCoord2f (1,0); glVertex3f(bodysize, bodysize, bodysize);
	glTexCoord2f (1,1); glVertex3f(bodysize, -bodysize, bodysize);
	
	//Back
	glNormal3f(0.0f, 0.0f, -1.0f);
	glTexCoord2f (0,1); glVertex3f(-bodysize, -bodysize, -bodysize);
	glTexCoord2f (0,0); glVertex3f(-bodysize, bodysize, -bodysize);
	glTexCoord2f (1,0); glVertex3f(bodysize, bodysize, -bodysize);
	glTexCoord2f (1,1); glVertex3f(bodysize, -bodysize, -bodysize);
	
	//Left
	glNormal3f(-1.0f, 0.0f, 0.0f);
	glTexCoord2f (0,1);	glVertex3f(-bodysize, -bodysize, -bodysize);
	glTexCoord2f (1,1); glVertex3f(-bodysize, -bodysize, bodysize);
	glTexCoord2f (1,0); glVertex3f(-bodysize, bodysize, bodysize);
	glTexCoord2f (0,0); glVertex3f(-bodysize, bodysize, -bodysize);
	
	//Top
	glNormal3f(0.0f, 1.0f, 0.0f);
	glTexCoord2f (0,1);	glVertex3f(-bodysize, bodysize, -bodysize);
	glTexCoord2f (1,1); glVertex3f(-bodysize, bodysize, bodysize);
	glTexCoord2f (1,0); glVertex3f(bodysize, bodysize, bodysize);
	glTexCoord2f (0,0); glVertex3f(bodysize, bodysize, -bodysize);

	//Bottom
	glNormal3f(0.0f, -1.0f, 0.0f);
	glTexCoord2f (0,1);	glVertex3f(-bodysize, -bodysize, -bodysize);
	glTexCoord2f (1,1); glVertex3f(-bodysize, -bodysize, bodysize);
	glTexCoord2f (1,0); glVertex3f(bodysize, -bodysize, bodysize);
	glTexCoord2f (0,0); glVertex3f(bodysize, -bodysize, -bodysize);
	glEnd();*/

	glColor3f(0.8f, 0.5f, 0.5f);
	// Draw Head
	glTranslatef(0.0f, 1.10f, 0.0f);
	glutSolidSphere(bodysize,20,20);
	//glDisable (GL_TEXTURE_2D);

	// Draw Arms
	glTranslatef(0.75f, 0.0f, 0.0f);
	glutSolidSphere(0.25f,20,20);
	glTranslatef(-1.5f, 0.0f, 0.0f);
	glutSolidSphere(0.25f,20,20);

	// Draw Eyes
	glPushMatrix();
		glColor3f(0.2f,0.2f,0.2f);
		glTranslatef(0.6f, 0.15f, 0.7f);
		glutSolidSphere(0.10f,10,10);
		glTranslatef(0.4f, 0.0f, 0.0f);
		glutSolidSphere(0.10f,10,10);
	glPopMatrix();
}

void DrawSkyBox()
{
	//Draw Skybox
	glBegin(GL_QUADS);
	glVertex3f(-halfBox, halfBox, halfBox);
	glVertex3f(halfBox, halfBox, halfBox);
	glVertex3f(halfBox, -halfBox, halfBox);
	glVertex3f(-halfBox, -halfBox, halfBox);

	glVertex3f(-halfBox, halfBox, -halfBox);
	glVertex3f(halfBox, halfBox, -halfBox);
	glVertex3f(halfBox, -halfBox, -halfBox);
	glVertex3f(-halfBox, -halfBox, -halfBox);

	glVertex3f(-halfBox, halfBox, halfBox);
	glVertex3f(halfBox, halfBox, halfBox);
	glVertex3f(halfBox, halfBox, -halfBox);
	glVertex3f(-halfBox, halfBox, -halfBox);

	glVertex3f(-halfBox, -halfBox, halfBox);
	glVertex3f(halfBox, -halfBox, halfBox);
	glVertex3f(halfBox, -halfBox, -halfBox);
	glVertex3f(-halfBox, -halfBox, -halfBox);

	glVertex3f(halfBox, -halfBox, halfBox);
	glVertex3f(halfBox, halfBox, halfBox);
	glVertex3f(halfBox, halfBox, -halfBox);
	glVertex3f(halfBox, -halfBox, -halfBox);

	glVertex3f(-halfBox, -halfBox, halfBox);
	glVertex3f(-halfBox, halfBox, halfBox);
	glVertex3f(-halfBox, halfBox, -halfBox);
	glVertex3f(-halfBox, -halfBox, -halfBox);
	glEnd();

}

void DrawGround()
{
	// Draw 900 tiles (30 x 30)
	for(int i = 0; i < numTiles; i++)
		for(int j = 0; j < numTiles; j++) 
		{
			glEnable(GL_TEXTURE_2D);
			glBindTexture(GL_TEXTURE_2D,g_texture_obj[0]);
			glPushMatrix();
				glTranslatef(i, 0.0, j);
				glColor3f(1.0f, 1.0f, 1.0f);
				glBegin(GL_QUADS);

					glNormal3f(0.0f, -1.0f, 0.0f);
					glTexCoord2f (0,1);	glVertex3f(-tileSize, -tileSize, -tileSize);
					glTexCoord2f (1,1); glVertex3f(-tileSize, -tileSize, tileSize);
					glTexCoord2f (1,0); glVertex3f(tileSize, -tileSize, tileSize);
					glTexCoord2f (0,0); glVertex3f(tileSize, -tileSize, -tileSize);
				glEnd();			
			glPopMatrix();
			glDisable (GL_TEXTURE_2D);
		}
}

void Shoot(Cannon &c)
{
	c.Set_cannonTimer(c.Get_cannonTimer() + 1);
	if(c.Get_cannonTimer() >= 30 && c.Get_shot() == false)
	{
		c.Set_cannonAngle(rand() % 90 - 45); //range from -45 to 45
		c.Set_cannonTimer(0);
	}

	c.Set_shootTimer(c.Get_shootTimer() + 1);
	if(c.Get_shootTimer() >= 10)
	{
		c.Set_shot(true);
		c.Shoot();

		//checks if cannon ball hits the boundary
		if(BoundaryProjCollision(c) || PlayerProjCollision(c))
		{
			c.Set_shootTimer(0);
			c.Set_shot(false);
			c.ResetProjPosition(); //resets the ball to cannon position
		}
	}
}

bool PlayerProjCollision(Cannon &c)
{
	if( player.getX() + player.get_player_size() >= c.GetProjPositionX() - 1.0f &&
		player.getX() <= c.GetProjPositionX() + 1.0f &&
		player.getZ() + player.get_player_size() >= c.GetProjPositionZ() - 1.0f &&
		player.getZ() <= c.GetProjPositionZ() + 1.0f )
	{
		life -= 1;
		return true;
	}
	else
		return false;
}

bool BoundaryProjCollision(Cannon &c)
{
	if( c.GetProjPositionX() >= numTiles ||
		c.GetProjPositionX() <= 0 ||
		c.GetProjPositionZ() >= numTiles ||
		c.GetProjPositionZ() <= 0 )
		return true;
	else
		return false;
}

void ResetGame()
{
	points = 0;
	life = 5;
	scale_var = 1;
	gameOver = false;

	player.SetX(numTiles/2);
	player.SetZ(numTiles/2);

	a1.ResetProjPosition();
	a2.ResetProjPosition();
	a3.ResetProjPosition();
	a4.ResetProjPosition();

	a1.Set_cannonTimer(0);
	a2.Set_cannonTimer(0);
	a3.Set_cannonTimer(0);
	a4.Set_cannonTimer(0);

	a1.Set_shootTimer(0);
	a2.Set_shootTimer(0);
	a3.Set_shootTimer(0);
	a4.Set_shootTimer(0);
}

//----------------------------------------------------------------------
// RESIZE
//----------------------------------------------------------------------
void ChangeWindowSize(int w1, int h1) {
	// Prevent a divide by zero, when window is too short
	// (you cant make a window of zero width).

	w=w1;
	h=h1;
	if(h1 == 0)      
	 h1 = 1;
	
	float ratio = 1.0 *w/ h;
	// Reset the coordinate system before modifying
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();

	// Set the viewport to be the entire window
	glViewport(0, 0, w, h);

	// Set the correct perspective.
	gluPerspective(45,ratio,1,1000);
	glMatrixMode(GL_MODELVIEW);
	cam.Update();
	
}

void WriteMenu(){
	glPushMatrix();
		SetOrthographicProjection();
		glLoadIdentity();
		glColor3f(1.0f,0.0f,0.0f);
		
 		sprintf(buffer,"%d", life);
		RenderBitmapString(w/4,20,GLUT_BITMAP_TIMES_ROMAN_24,"Lives: ");
		RenderBitmapString(w/4+100,20,GLUT_BITMAP_TIMES_ROMAN_24,buffer);

		sprintf(buffer,"%d", points);
		RenderBitmapString(3*w/4,20,GLUT_BITMAP_TIMES_ROMAN_24,"Points: ");
		RenderBitmapString(3*w/4+100,20,GLUT_BITMAP_TIMES_ROMAN_24,buffer);
		
		if(gameOver)
		{
			glColor3f(1.0f, 0.0f, 0.0f);
			RenderBitmapString(w/2,h/2-50,GLUT_BITMAP_TIMES_ROMAN_24,"GAME OVER");
			glColor3f(0.0f,0.0f,0.0f);
			RenderBitmapString(w/2-50,h/2+50,GLUT_BITMAP_TIMES_ROMAN_24,"Press R to Restart or Esc to Quit");
		}


		ResetPerspectiveProjection();
	glPopMatrix();
}

void SetOrthographicProjection() {
	// switch to projection mode
	glMatrixMode(GL_PROJECTION);
	// save previous matrix which contains the 
	//settings for the perspective projection
	glPushMatrix();
	// reset matrix
	glLoadIdentity();
	// set a 2D orthographic projection
	gluOrtho2D(0, w, 0, h);
	// invert the y axis, down is positive
	glScalef(1, -1, 1);
	// mover the origin from the bottom left corner
	// to the upper left corner
	glTranslatef(0, -h, 0);
	glMatrixMode(GL_MODELVIEW);
}

void RenderBitmapString(float x, float y, void *font, char *string) {  
  char *c;
  glRasterPos2f(x, y);
  for (c=string; *c != '\0'; c++) {
    glutBitmapCharacter(font, *c);
  }
}

void ResetPerspectiveProjection() {
	glMatrixMode(GL_PROJECTION);
	glPopMatrix();
	glMatrixMode(GL_MODELVIEW);
}

//----------------------------------------------------------------------
// FOG
//----------------------------------------------------------------------
void loadTexture(GLuint texture_obj, const char *fileName) {
	SDL_Surface *g_image_surface = NULL; 
    g_image_surface = IMG_Load(fileName);

    glBindTexture(GL_TEXTURE_2D,texture_obj);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT);
    glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE);
    glTexImage2D(GL_TEXTURE_2D, 0, g_image_surface->format->BytesPerPixel, g_image_surface->w, g_image_surface->h,0,GL_RGB,GL_UNSIGNED_BYTE,g_image_surface->pixels);
    
    SDL_FreeSurface(g_image_surface);
}

void timer(int id)
{
	glutPostRedisplay();
}